package com.yanqu.road.server.manger.activity.ppl;

import com.yanqu.road.entity.activity.ppl.PplLine;
import com.yanqu.road.entity.activity.ppl.PplUserData;
import com.yanqu.road.entity.activity.ppl.shootlogic.PplScreenPos;
import com.yanqu.road.entity.activity.ppl.shootlogic.PplTangentPoint;
import com.yanqu.road.utils.math.BigDecimalMath;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 泡泡龙发射球算法
 */
public class PplShootLogic {

    // 用户ID，本次递归次数
    private static Map<Long, Integer> diGuiTimes = new ConcurrentHashMap<>();

    // 用户ID，本次屏幕高度
    private static Map<Long, BigDecimal> screenMaxYMap = new ConcurrentHashMap<>();

    private static Logger log = LogManager.getLogger(PplShootLogic.class.getName());

    private static final BigDecimal screenMaxX = new BigDecimal(750);
    //    private static final BigDecimal screenMaxY = new BigDecimal(1000); // 不同用户不同
    private static final BigDecimal screenMinX = new BigDecimal(0);
    private static final BigDecimal screenMinY = new BigDecimal(0);

    private static final BigDecimal r = new BigDecimal(35); // 半径
    private static final BigDecimal d = r.multiply(new BigDecimal(2)); // 直径
    private static final BigDecimal screenYSpacing = new BigDecimal(61); // 两球y距离差
    private static final BigDecimal checkD = new BigDecimal(32 * 2); // 碰撞检测直径

    private static final BigDecimal ballMaxX = screenMaxX.subtract(r);
    //    private static final BigDecimal ballMaxY = screenMaxY.subtract(r); // 不同用户不同
    private static final BigDecimal ballMinX = screenMinX.add(r);
    private static final BigDecimal ballMinY = screenMinY.add(r);

    // 计算过程精度，太小的话容易误差很大
    private static final int precision = 20;

    // 发射点坐标
    private static final BigDecimal shootX = screenMaxX.divide(new BigDecimal(2), precision, BigDecimal.ROUND_HALF_UP);
    private static final BigDecimal shootY = screenMinY;

    /**
     * 获取移动路径
     *
     * @param angleFromClient 客户端的角度
     * @return 弹射点+被碰撞点+碰撞时自身的点+最终停留点（飞出屏幕外时：弹射点+屏幕外的点）
     */
    public static List<PplScreenPos> getMoveKeyPos(BigDecimal angleFromClient, PplUserData pplUserData, BigDecimal height) {
        screenMaxYMap.put(pplUserData.getUserId(), height);
        BigDecimal shootAngle = parseAngleToServer(angleFromClient);
        List<PplScreenPos> route = new ArrayList<>();
        // 90度特殊处理
        if (shootAngle.compareTo(new BigDecimal(90)) == 0) {
            calMoveKeyPosByZeroAngle(pplUserData, route);
        } else {
            PplScreenPos baseBall = new PplScreenPos(shootX, shootY);
            BigDecimal k = new BigDecimal(Math.tan(Math.toRadians(shootAngle.doubleValue()))).setScale(precision, BigDecimal.ROUND_HALF_UP);
            diGuiTimes.put(pplUserData.getUserId(), 0);
            calMoveKeyPos(pplUserData, route, baseBall, k, k.compareTo(BigDecimal.ZERO) > 0 ? 1 : 2);
            log.info("userId = {} ，di gui times = {}", pplUserData.getUserId(), diGuiTimes.get(pplUserData.getUserId()));
        }
        changeScreenPosListToClient(route, pplUserData.getUserId());
        return route;
    }

    /**
     * 垂直发射特殊处理
     */
    private static void calMoveKeyPosByZeroAngle(PplUserData userData, List<PplScreenPos> route) {
        // y值最小，x差值最小的，优先碰撞
        BigDecimal leftX = shootX.subtract(d);
        BigDecimal rightX = shootX.add(d);
        PplScreenPos collidePos = null;
        BigDecimal minSubX = new BigDecimal(Integer.MAX_VALUE);
        List<PplLine> pplLineList = userData.getPplLineList();
        for (PplLine pplLine : pplLineList) {
            int[] ballArray = pplLine.getBallArray();
            for (int i = 0; i < ballArray.length; i++) {
                // 调过空位置
                if (ballArray[i] == 0) {
                    continue;
                }
                PplScreenPos pplScreenPos = getPplScreenPos(i, pplLine.getY(), userData.getCurMaxY(), userData.getUserId());
                // 可能碰撞
                if (pplScreenPos.getScreenX().compareTo(leftX) >= 0 && pplScreenPos.getScreenX().compareTo(rightX) <= 0) {
                    // 判断差值
                    BigDecimal subX = pplScreenPos.getScreenX().subtract(shootX).abs();
                    if (subX.compareTo(minSubX) < 0) {
                        collidePos = pplScreenPos; // y相同时，x差值最小的优先碰到
                    }
                }
            }
            // 已经有最小y了，后面的y更大不判断了
            if (collidePos != null) {
                route.add(collidePos); // add 被撞点

                BigDecimal subY;
                if (collidePos.getScreenX().compareTo(shootX) == 0) {
                    // 正下方上去
                    subY = d;
                } else if (minSubX.compareTo(d) == 0) {
                    // 相切过去
                    subY = BigDecimal.ZERO;
                } else {
                    // 其他角度勾股定理算Y差值
                    subY = BigDecimalMath.sqrt(d.pow(2).add(minSubX.pow(2)));
                }
                PplScreenPos self = new PplScreenPos(shootX, collidePos.getScreenY().subtract(d));
                route.add(self); // add 自身位置

                PplScreenPos stopPos = calStopPos(userData, self, collidePos);
                if (stopPos == null) {
                    log.error("PplShootLogic error on addCollidePos, stopPos is null!"); // 一般不会null
                    route.clear();
                    return;
                }
                route.add(stopPos); // add 最终停留位置
                return;
            }
        }
        // 没有碰撞，就是飞出去了
        route.add(new PplScreenPos(shootX, getScreenMaxY(userData.getUserId()).add(new BigDecimal(100))));
    }

    /**
     * 获取移动路径关键点
     */
    private static void calMoveKeyPos(PplUserData pplUserData, List<PplScreenPos> route, PplScreenPos ballPos, BigDecimal k, int direction) {
        if (diGuiTimes.get(pplUserData.getUserId()) > 50) {
            log.error("PplShootLogic error on calMoveKeyPos, di gui times too much!");
            route.clear();
            return;
        }
        diGuiTimes.put(pplUserData.getUserId(), diGuiTimes.get(pplUserData.getUserId()) + 1);
        // 发射点引出两条直线，间距4r，圆心在其中的点会碰撞
        BigDecimal b = getBByXYK(ballPos.getScreenX(), ballPos.getScreenY(), k);
        // 垂直距离加减值 vValue = 根号( (k^2+1) * d^2 )
        BigDecimal vValue = BigDecimalMath.sqrt(k.pow(2).add(BigDecimal.ONE).multiply(checkD.pow(2)));
        // 可能碰撞的点
        List<PplScreenPos> mayCollidePosList = calMayCollidePosList(pplUserData, k, b.add(vValue), b.subtract(vValue));
        // 有碰撞了
        if (!mayCollidePosList.isEmpty()) {
            addCollideAndStopPos(pplUserData, route, mayCollidePosList, ballPos, k, b);
            return;
        }
        // 没有碰撞，计算弹射点
        PplScreenPos edgePos = calEdgeScreenPos(b, k, direction, pplUserData.getUserId());
        if (edgePos == null) {
            log.error("PplShootLogic error on calMoveKeyPos, edgePos is null!"); // 一般不会null
            route.clear();
            return;
        }
        // 飞出屏幕，则特殊处理（弹射点在顶部）
        if (edgePos.getScreenY().compareTo(getBallMaxY(pplUserData.getUserId())) == 0) {
            PplScreenPos outScreenPos = new PplScreenPos();
            outScreenPos.setScreenY(getScreenMaxY(pplUserData.getUserId()).add(new BigDecimal(100)));
            outScreenPos.setScreenX(getXByYKB(outScreenPos.getScreenY(), k, b));
            route.add(outScreenPos);
            return;
        }
        // 计算新的k、新的direction
        k = k.multiply(new BigDecimal(-1));
        direction = calNewDirection(edgePos, direction);
        if (direction < 1 || direction > 4) {
            log.error("PplShootLogic error on calMoveKeyPos, edgePos is null!"); // 一般不会null
            route.clear();
            return;
        }
        // 递归
        route.add(edgePos);
        calMoveKeyPos(pplUserData, route, edgePos, k, direction);
    }

    /**
     * 允许底部新增一行的情况下，坐标合法且为空
     */
    private static boolean isXyLegalEmpty(PplUserData userData, int x, int y) {
        List<PplLine> pplLineList = userData.getPplLineList();
        int startY = pplLineList.get(0).getY();
        int maxY = userData.getCurMaxY();
        // 下方新的一行，x合法就行
        if (y == startY - 1) {
            return x >= 0 && x < pplLineList.get(0).getBallArray().length;
        }
        // y合法
        if (y < startY - 1 || y > maxY) {
            return false;
        }
        // x合法且为空
        PplLine pplLine = pplLineList.get(y - startY);
        if (x >= 0 && x < pplLine.getBallArray().length) {
            return pplLine.getBallArray()[x] == 0;
        } else {
            return false;
        }
    }

    // ===============屏幕坐标相关===============

    /**
     * 获取停留点
     *
     * @param selfCollidePos 碰撞时自身位置
     * @param toPos          被撞点
     */
    public static PplScreenPos calStopPos(PplUserData userData, PplScreenPos selfCollidePos, PplScreenPos toPos) {
        int maxY = userData.getCurMaxY();

        // 获取碰撞点附近的6个点
        int x = toPos.getX();
        int y = toPos.getY();
        int[] yArray = {y - 1, y - 1, y, y, y + 1, y + 1};
        int[] xArray;
        if (y % 2 == 0) {
            int[] tmpArray = {x, x + 1, x - 1, x + 1, x, x + 1};
            xArray = tmpArray;
        } else {
            int[] tmpArray = {x - 1, x, x - 1, x + 1, x - 1, x};
            xArray = tmpArray;
        }

        // 从x轴正方向逆时针12等分
        int[][] checkArray = {
                {3, 5, 1}, // 0
                {5, 3, 4}, // 1
                {5, 4, 3}, // 2
                {4, 5, 2}, // 3
                {4, 2, 5}, // 4
                {2, 4, 0}, // 5
                {2, 0, 4}, // 6
                {0, 2, 1}, // 7
                {0, 1, 2}, // 8
                {1, 0, 3}, // 9
                {1, 3, 0}, // 10
                {3, 1, 5}, // 11
        };

        int checkIndex = 0;
        BigDecimal tanA = selfCollidePos.getScreenY().subtract(toPos.getScreenY())
                .divide(selfCollidePos.getScreenX().subtract(toPos.getScreenX()), precision, BigDecimal.ROUND_HALF_UP);
        int xSign = selfCollidePos.getScreenX().compareTo(toPos.getScreenX());
        int ySign = selfCollidePos.getScreenY().compareTo(toPos.getScreenY());

        // 判断需要检查哪些点
        if (xSign == 0) {
            if (ySign > 0) {
                checkIndex = 2; // 【正上方被撞】
            } else {
                checkIndex = 9; // 【正下方被撞】
            }
        } else if (ySign == 0) {
            if (xSign < 0) {
                checkIndex = 6; // 【正左边被撞】
            } else { // 左边被撞
                checkIndex = 11; // 【正右边被撞】
            }
        } else { // 【其他角度被撞】
//            if (tanA.compareTo(tanF60) <= 0) {
//                checkIndex = xSign > 0 ? 9 : 3;
//            } else if (tanA.compareTo(tanF30) <= 0) {
//                checkIndex = xSign > 0 ? 10 : 4;
//            } else if (tanA.compareTo(BigDecimal.ZERO) <= 0) {
//                checkIndex = xSign > 0 ? 11 : 5;
//            } else if (tanA.compareTo(tan30) <= 0) {
//                checkIndex = xSign > 0 ? 0 : 6;
//            } else if (tanA.compareTo(tan60) <= 0) {
//                checkIndex = xSign > 0 ? 1 : 7;
//            } else {
//                checkIndex = xSign > 0 ? 2 : 8;
//            }
            if (tanA.compareTo(tanF77) <= 0) {
                checkIndex = xSign > 0 ? 9 : 3;
            } else if (tanA.compareTo(tanF45) <= 0) {
                checkIndex = xSign > 0 ? 10 : 4;
            } else if (tanA.compareTo(BigDecimal.ZERO) <= 0) {
                checkIndex = xSign > 0 ? 11 : 5;
            } else if (tanA.compareTo(tan45) <= 0) {
                checkIndex = xSign > 0 ? 0 : 6;
            } else if (tanA.compareTo(tan77) <= 0) {
                checkIndex = xSign > 0 ? 1 : 7;
            } else {
                checkIndex = xSign > 0 ? 2 : 8;
            }
        }

        // 找位置
        int[] check = checkArray[checkIndex];
        int width = userData.getPplLineList().get(0).getBallArray().length;
        for (int i = 0; i < checkArray.length; i++) {
            // 如果球在最左边，且碰撞点也在左边，防止球跳到另一边
            if (toPos.getX() == 0 && (
                    checkIndex == 5 && check[i] == 0
                            || checkIndex == 6 && check[i] == 4)) {
                continue;
            }
            // 如果球在最右边...同上
            if (toPos.getX() == width && (
                    checkIndex == 0 && check[i] == 1
                            || checkIndex == 11 && check[i] == 5)) {
                continue;
            }
            if (isXyLegalEmpty(userData, xArray[check[i]], yArray[check[i]])) {
                return getPplScreenPos(xArray[check[i]], yArray[check[i]], maxY, userData.getUserId());
            }
        }
        return null;
    }

    /**
     * 计算可能与自己相撞的点
     *
     * @param upB   两条kx+b直线较大的b
     * @param downB 两条kx+b直线较小的b
     */
    private static List<PplScreenPos> calMayCollidePosList(PplUserData pplUserData, BigDecimal k, BigDecimal upB, BigDecimal downB) {
        List<PplScreenPos> canCollisionList = new ArrayList<>();
        for (PplLine pplLine : pplUserData.getPplLineList()) {
            int[] ballArray = pplLine.getBallArray();
            for (int j = 0; j < ballArray.length; j++) {
                if (ballArray[j] != 0) {
                    PplScreenPos screenPos = getPplScreenPos(j, pplLine.getY(), pplUserData.getCurMaxY(), pplUserData.getUserId());
                    // 判断是否在两直线间
                    BigDecimal upY = k.multiply(screenPos.getScreenX()).add(upB);
                    BigDecimal downY = k.multiply(screenPos.getScreenX()).add(downB);
                    if (upY.compareTo(screenPos.getScreenY()) > 0 && downY.compareTo(screenPos.getScreenY()) < 0) {
                        canCollisionList.add(screenPos);
                    }
                }
            }
        }
        return canCollisionList;
    }

    /**
     * 添加碰撞相关位置（1.被撞球的位置，2.碰撞时自己的位置，3.最终自己停留位置）
     *
     * @param fromPos           出发点
     * @param mayCollidePosList 可能拍碰撞的点
     */
    private static void addCollideAndStopPos(PplUserData userData, List<PplScreenPos> route, List<PplScreenPos> mayCollidePosList, PplScreenPos fromPos, BigDecimal k, BigDecimal b) {
        // 计算每个可能碰撞点放大到2d直径的圆 与 射线 交点，较近的点到射线出发点的距离，最近的优先相切
        PplTangentPoint minTangentPos = null;
        BigDecimal minLength2 = new BigDecimal(Integer.MAX_VALUE);
        for (PplScreenPos pos : mayCollidePosList) {
            PplTangentPoint pplTangentPoint = calTangentPoint(fromPos, pos, k, b);
            if (pplTangentPoint != null && pplTangentPoint.getLength2().compareTo(minLength2) < 0) {
                minTangentPos = pplTangentPoint;
                minLength2 = minTangentPos.getLength2();
            }
        }
        if (minTangentPos == null) {
            log.error("PplShootLogic error on addCollidePos, minTangentPos is null!"); // 一般不会null
            route.clear();
            return;
        }
        route.add(minTangentPos.getBeTangentPoint()); // add 被撞球的位置
        route.add(minTangentPos.getTangentPoint()); // add 碰撞时自身的位置
        PplScreenPos stopPos = calStopPos(userData, minTangentPos.getTangentPoint(), minTangentPos.getBeTangentPoint());
        if (stopPos == null) {
            log.error("PplShootLogic error on addCollidePos, stopPos is null!"); // 一般不会null
            route.clear();
            return;
        }
        route.add(stopPos); // add 最终停留位置
    }

    /**
     * 计算2d直径的圆与射线的较近切点
     *
     * @param fromPos 碰撞点
     * @param toPos   被撞球
     */
    private static PplTangentPoint calTangentPoint(PplScreenPos fromPos, PplScreenPos toPos, BigDecimal k, BigDecimal b) {
        PplTangentPoint pplTangentPoint = new PplTangentPoint();
        pplTangentPoint.setBeTangentPoint(toPos);
        // 将y=kx+b代入圆方程，得到关于x的一元二次方程ax^2+bx+c=0
        BigDecimal aa = k.pow(2).add(BigDecimal.ONE); // aa = k^2 + 1
        BigDecimal bb = new BigDecimal(2).multiply(k).multiply(b)
                .subtract(new BigDecimal(2).multiply(k).multiply(toPos.getScreenY()))
                .subtract(new BigDecimal(2).multiply(toPos.getScreenX())); // bb = 2kb - 2ky0 - 2x0
        BigDecimal cc = toPos.getScreenX().pow(2)
                .add(toPos.getScreenY().pow(2))
                .add(b.pow(2))
                .subtract(new BigDecimal(2).multiply(toPos.getScreenY()).multiply(b))
                .subtract(d.pow(2)); // cc = x0^2 + y0^2 + b^2 - 2y0b - r^2
        // b^2 - 4ac
        BigDecimal b24ac = bb.pow(2).subtract(new BigDecimal(4).multiply(aa).multiply(cc));
        if (b24ac.compareTo(BigDecimal.ZERO) < 0) {
            return null;
        }
        // 韦达定理
        BigDecimal x1 = new BigDecimal(-1).multiply(bb).add(BigDecimalMath.sqrt(b24ac))
                .divide(new BigDecimal(2).multiply(aa), precision, BigDecimal.ROUND_HALF_UP);
        BigDecimal x2 = new BigDecimal(-1).multiply(bb).subtract(BigDecimalMath.sqrt(b24ac))
                .divide(new BigDecimal(2).multiply(aa), precision, BigDecimal.ROUND_HALF_UP);
        BigDecimal y1 = getYByXKB(x1, k, b);
        BigDecimal y2 = getYByXKB(x2, k, b);
        // 离发射点近的交点优先到达    length^2 = (y-y0)^2+(x-x0)^2
        BigDecimal len1 = y1.subtract(fromPos.getScreenY()).pow(2)
                .add(x1.subtract(fromPos.getScreenX()).pow(2));
        BigDecimal len2 = y2.subtract(fromPos.getScreenY()).pow(2)
                .add(x2.subtract(fromPos.getScreenX()).pow(2));
        if (len1.compareTo(len2) <= 0) {
            pplTangentPoint.setLength2(len1);
            pplTangentPoint.setTangentPoint(new PplScreenPos(x1, y1));
        } else {
            pplTangentPoint.setLength2(len2);
            pplTangentPoint.setTangentPoint(new PplScreenPos(x2, y2));
        }
        return pplTangentPoint;
    }

    /**
     * 计算自身碰撞时位置
     *
     * @param fromPos 碰撞点
     * @param toPos   被撞球
     */
    private static PplScreenPos calSelfCollidePos(PplScreenPos fromPos, PplScreenPos toPos, BigDecimal k, BigDecimal b) {
        // 将y=kx+b代入圆方程，得到关于x的一元二次方程ax^2+bx+c=0
        BigDecimal aa = k.pow(2).add(BigDecimal.ONE); // aa = k^2 + 1
        BigDecimal bb = new BigDecimal(2).multiply(k).multiply(b)
                .subtract(new BigDecimal(2).multiply(k).multiply(toPos.getScreenY()))
                .subtract(new BigDecimal(2).multiply(toPos.getScreenX())); // bb = 2kb - 2ky0 - 2x0
        BigDecimal cc = toPos.getScreenX().pow(2)
                .add(toPos.getScreenY().pow(2))
                .add(b.pow(2))
                .subtract(new BigDecimal(2).multiply(toPos.getScreenY()).multiply(b))
                .subtract(d.pow(2)); // cc = x0^2 + y0^2 + b^2 - 2y0b - r^2
        // b^2 - 4ac
        BigDecimal b24ac = bb.pow(2).subtract(new BigDecimal(4).multiply(aa).multiply(cc));
        if (b24ac.compareTo(BigDecimal.ZERO) < 0) {
            return null;
        }
        // 韦达定理
        BigDecimal x1 = new BigDecimal(-1).multiply(bb).add(BigDecimalMath.sqrt(b24ac))
                .divide(new BigDecimal(2).multiply(aa), precision, BigDecimal.ROUND_HALF_UP);
        BigDecimal x2 = new BigDecimal(-1).multiply(bb).subtract(BigDecimalMath.sqrt(b24ac))
                .divide(new BigDecimal(2).multiply(aa), precision, BigDecimal.ROUND_HALF_UP);
        BigDecimal y1 = getYByXKB(x1, k, b);
        BigDecimal y2 = getYByXKB(x2, k, b);
        // 离发射点近的交点优先到达    length^2 = (y-y0)^2+(x-x0)^2
        BigDecimal len1 = y1.subtract(fromPos.getScreenY()).pow(2)
                .add(x1.subtract(fromPos.getScreenX()).pow(2));
        BigDecimal len2 = y2.subtract(fromPos.getScreenY()).pow(2)
                .add(x2.subtract(fromPos.getScreenX()).pow(2));
        return len1.compareTo(len2) <= 0 ? new PplScreenPos(x1, y1) : new PplScreenPos(x2, y2);
    }

    /**
     * 计算跟屏幕边缘的交点
     */
    private static PplScreenPos calEdgeScreenPos(BigDecimal b, BigDecimal k, int direction, long userId) {
        BigDecimal ballMaxY = getBallMaxY(userId);
        switch (direction) {
            case 1:
                // 检查右边、上边
                BigDecimal y1 = getYByXKB(ballMaxX, k, b);
                if (y1.compareTo(ballMaxY) < 0) {
                    return new PplScreenPos(ballMaxX, y1);
                }
                BigDecimal x1 = getXByYKB(ballMaxY, k, b);
                if (x1.compareTo(ballMaxX) < 0) {
                    return new PplScreenPos(x1, ballMaxY);
                }
                break;
            case 2:
                // 检查上边、左边
                BigDecimal x2 = getXByYKB(ballMaxY, k, b);
                if (x2.compareTo(ballMinX) > 0) {
                    return new PplScreenPos(x2, ballMaxY);
                }
                BigDecimal y2 = getYByXKB(ballMinX, k, b);
                if (y2.compareTo(ballMaxY) < 0) {
                    return new PplScreenPos(ballMinX, y2);
                }
                break;
            case 3:
                // 检查左边、下边
                BigDecimal y3 = getYByXKB(ballMinX, k, b);
                if (y3.compareTo(ballMinY) > 0) {
                    return new PplScreenPos(ballMinX, y3);
                }
                BigDecimal x3 = getXByYKB(ballMinY, k, b);
                if (x3.compareTo(ballMinX) > 0) {
                    return new PplScreenPos(x3, ballMinY);
                }
                break;
            case 4:
                // 检查下边、右边
                BigDecimal x4 = getXByYKB(ballMinY, k, b);
                if (x4.compareTo(ballMaxX) < 0) {
                    return new PplScreenPos(x4, ballMinY);
                }
                BigDecimal y4 = getYByXKB(ballMaxX, k, b);
                if (y4.compareTo(ballMinY) > 0) {
                    return new PplScreenPos(ballMaxX, y4);
                }
                break;
            default:
                break;
        }
        return null;
    }

    /**
     * 获取屏幕坐标
     */
    public static PplScreenPos getPplScreenPos(int x, int y, int maxY, long userId) {
        PplScreenPos pplScreenPos = new PplScreenPos();
        pplScreenPos.setX(x);
        pplScreenPos.setY(y);
        if (y % 2 == 0) {
            pplScreenPos.setScreenX(d.multiply(new BigDecimal(x)).add(d));
        } else {
            pplScreenPos.setScreenX(d.multiply(new BigDecimal(x)).add(r));
        }
        pplScreenPos.setScreenY(getScreenMaxY(userId).subtract(screenYSpacing.multiply(new BigDecimal(maxY - y)).add(r)));
        return pplScreenPos;
    }

    /**
     * 计算新方向
     *
     * @param edgeScreenPos 屏幕边缘相切的球心
     * @param oldDirection  旧的方向
     */
    private static int calNewDirection(PplScreenPos edgeScreenPos, int oldDirection) {
        BigDecimal x = edgeScreenPos.getScreenX();
        switch (oldDirection) {
            case 1:
                return x.compareTo(ballMaxX) == 0 ? 2 : 4;
            case 2:
                return x.compareTo(ballMinX) == 0 ? 1 : 3;
            case 3:
                return x.compareTo(ballMinX) == 0 ? 4 : 2;
            case 4:
                return x.compareTo(ballMaxX) == 0 ? 3 : 1;
            default:
                break;
        }
        return 0;
    }

    // ==============前后端数据转换==============

    /**
     * 转换为服务端角度（客户端垂直发射时0度，左边负，右边正）
     */
    private static BigDecimal parseAngleToServer(BigDecimal angleFromClient) {
        return angleFromClient.multiply(new BigDecimal(-1)).add(new BigDecimal(90));
    }

    /**
     * 直角坐标转换为客户端坐标（顶部y=0）
     */
    private static void changeScreenPosListToClient(List<PplScreenPos> route, long userId) {
        BigDecimal screenMaxY = getScreenMaxY(userId);
        for (PplScreenPos pos : route) {
            pos.setScreenY(screenMaxY.subtract(pos.getScreenY()));
        }
    }

    // =================初中数学=================

    private static final BigDecimal tan30 = BigDecimal.ONE.divide(BigDecimalMath.sqrt(new BigDecimal(3)), precision, BigDecimal.ROUND_HALF_UP);// 1/根号3
    private static final BigDecimal tanF30 = tan30.multiply(new BigDecimal(-1));
    private static final BigDecimal tan60 = BigDecimalMath.sqrt(new BigDecimal(3)); // 根号3
    private static final BigDecimal tanF60 = tan60.multiply(new BigDecimal(-1));

    private static final BigDecimal tan45 = BigDecimal.ONE;
    private static final BigDecimal tanF45 = tan45.multiply(new BigDecimal(-1));
    private static final BigDecimal tan77 = new BigDecimal(Math.tan(Math.toRadians(77)));
    private static final BigDecimal tanF77 = tan77.multiply(new BigDecimal(-1));

    /**
     * 点斜法求常数 b = y - kx
     */
    private static BigDecimal getBByXYK(BigDecimal x, BigDecimal y, BigDecimal k) {
        return y.subtract(x.multiply(k));
    }

    /**
     * 计算y值 y = kx + b
     */
    private static BigDecimal getYByXKB(BigDecimal x, BigDecimal k, BigDecimal b) {
        return x.multiply(k).add(b);
    }

    /**
     * 计算x值 x = (y - b) / k
     */
    private static BigDecimal getXByYKB(BigDecimal y, BigDecimal k, BigDecimal b) {
        return y.subtract(b).divide(k, precision, BigDecimal.ROUND_HALF_UP);
    }

    // =================高度适配=================

    private static BigDecimal getScreenMaxY(long userId) {
        return screenMaxYMap.get(userId);
    }

    private static BigDecimal getBallMaxY(long userId) {
        return screenMaxYMap.get(userId).subtract(r);
    }

}
